/*
 * 
 *   Copyright 2007-2008 University Of Southern California
 * 
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 *   Unless required by applicable law or agreed to in writing,
 *   software distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 * 
 */

package edu.isi.pegasus.planner.catalog.site.classes;

import edu.isi.pegasus.planner.catalog.site.classes.FileServerType.OPERATION;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import edu.isi.pegasus.planner.common.PegRandom;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
        
/**
 * An abstract base class that creates a directory type. It associates multiple
 * file servers and an internal mount point.
 * 
 * @author Karan Vahi
 */
public abstract class DirectoryLayout extends AbstractSiteData{
    
    /**
     * The list of file servers that can be used to write to access this directory
     * indexed by operation type.
     */
    //protected List<FileServer> mFileServers;
    protected Map<FileServer.OPERATION, List<FileServer>> mFileServers;

    
    /**
     * The internal mount point for the directory.
     */
    protected InternalMountPoint mInternalMount;    
    
    /**
     * The default constructor.
     */
    public DirectoryLayout(){
        initialize( );
    }

    /**
     * The copy constructor
     */
    public DirectoryLayout( DirectoryLayout directory ){
        this.initialize( directory.mFileServers, directory.getInternalMountPoint() );
    }
    
    /**
     * The overloaded constructor
     * 
     * @param  fs  map of file servers indexed by FileServer operation
     * @param  imt the internal mount point.
     */
    public DirectoryLayout(  Map<FileServer.OPERATION, List<FileServer>> fs, InternalMountPoint imt ){
        initialize( fs, imt );
    }
    
    /**
     *  Initializes the object
     * 
     */
    private void initialize( ){
        this.mFileServers = new  HashMap<FileServer.OPERATION, List<FileServer>> ();
        this.resetFileServers();
        this.mInternalMount =  new InternalMountPoint();

    }
    
    /**
     *  Initializes the object
     * 
     * @param  fs  list of file servers indexed by operation type
     * @param  imt the internal mount point.
     */
    private void initialize(  Map<FileServer.OPERATION, List<FileServer>> fs, InternalMountPoint imt ){
        this.mFileServers = fs;
        this.mInternalMount = imt ;
    }
    
    /**
     * Adds a FileServer that can access this directory.
     * 
     * @param server   the file server.
     */
    public void addFileServer( FileServer server ){
        List<FileServer> l = mFileServers.get( server.getSupportedOperation());
        l.add(server);
    }

    /**
     * Sets the list of FileServers that can access this directory.
     * 
     * @param servers   the list of servers
     */
    public void setFileServers( List<FileServer> servers ){
        this.resetFileServers();
        for( FileServer server: servers ){
            this.addFileServer(server);
        }
    }
    
    /**
     * Selects a random file server and returns it matching an operation type.
     * If not found matching the operation type it defaults back to the all operation
     * server.
     *
     * @param operation  the operation for which the file server is required
     *
     * @return FileServer else null
     */
    public FileServer selectFileServer( FileServer.OPERATION operation ){
        List<FileServer> servers = getFileServers(operation);
        
        if (servers == null || servers.isEmpty() ) {
        	servers = getFileServers( FileServer.OPERATION.all );
        }
        	
        return ( servers == null || servers.isEmpty() )?
                null:
                servers.get( PegRandom.getInteger( servers.size() - 1) );
        /*
        return ( this.mFileServers == null || this.mFileServers.size() == 0 )?
                 null :
                 this.mFileServers.get(  PegRandom.getInteger( this.mFileServers.size() - 1) );
         */
    }

    /**
     * Selects all file servers and returns it matching an operation type.
     *
     * @param operation  the operation for which the file server is required
     *
     * @return List of FileServer else null
     */
    public List<FileServer> getFileServers( FileServer.OPERATION operation ){
        List<FileServer> servers = this.mFileServers.get(operation);

        return servers;
    }
    
    /**
     * A convenience method that retrieves whether the directory has a
     * file server for get operations ( file server with type get and all )
     *
     * @return boolean
     */
    public boolean hasFileServerForGETOperations(){
        return this.hasFileServerForOperations( OPERATION.get );
    }

     /**
     * A convenience method that retrieves whether the directory has a
     * file server for put operations ( file server with type put and all )
     *
     * @return boolean
     */
    public boolean hasFileServerForPUTOperations(){
        return this.hasFileServerForOperations( OPERATION.put );
    }
    
    /**
     * A convenience method that retrieves whether the directory has a
     * file server for a particular operation. Servers with operation type as ALL
     * are also considered.
     * 
     *
     * @param operation  the operation for which we need the file servers
     * 
     * @return boolean
     */
    public boolean hasFileServerForOperations( FileServer.OPERATION operation ){
        return !(this.mFileServers.get( FileServer.OPERATION.all ).isEmpty()
                && this.mFileServers.get( operation ).isEmpty());
    }
    
    /**
     * Returns at iterator to the file servers.
     * 
     * @return Iterator<FileServer>
     */
    public Iterator<FileServer> getFileServersIterator( FileServer.OPERATION operation ){
        return mFileServers.get( operation).iterator();
    }
    
    /**
     * Sets the internal mount point for the directory.
     * 
     * @param mountPoint  the internal mount point.
     */
    public void setInternalMountPoint( InternalMountPoint mountPoint ){
        mInternalMount = mountPoint;
    }
    
    /**
     * Returns the internal mount point for the directory.
     * 
     * @return the internal mount point.
     */
    public InternalMountPoint getInternalMountPoint(){
        return this.mInternalMount;
    }
    
    /***
     * A convenience method that returns true if all the attributes values are 
     * uninitialized or empty strings. Useful for serializing the object as
     * XML.
     * 
     * @return boolean
     */
    public boolean isEmpty(){
        boolean result = true;

        //we need to check if each file servers for each supported
        //operation are empty or not.
        for( FileServer.OPERATION operation : FileServer.OPERATION.values() ){
            result = result && this.mFileServers.get( operation ).isEmpty();
            if( !result ){
                //break out of the computation.
                break;
            }
        }

        return result && this.getInternalMountPoint().isEmpty();
    }

    /**
     * Resets the internal collection of file servers
     */
    public void resetFileServers(){
        for( OPERATION op : FileServer.OPERATION.values() ){
            this.mFileServers.put( op, new LinkedList<FileServer>() );
        }
    }

 

    /**
     * Returns the clone of the object.
     *
     * @return the clone
     */
    public Object clone(){
        DirectoryLayout obj;
        try{
            obj = ( DirectoryLayout ) super.clone();
        }
        catch( CloneNotSupportedException e ){
            //somewhere in the hierarch chain clone is not implemented
            throw new RuntimeException("Clone not implemented in the base class of " + this.getClass().getName(),
                                       e );
        }
        obj.initialize( );        
        obj.setInternalMountPoint( (InternalMountPoint)mInternalMount.clone() );

        for( OPERATION op : FileServer.OPERATION.values() ){
            List<FileServer> servers = this.mFileServers.get( op );
            for( FileServer server : servers ){
                obj.addFileServer( (FileServer)server.clone() );
            }
        }
        
        return obj;
    }


}
